import { pauseTracking, resumeTracking, track, trigger } from "./effect.js";
import { TrackOpTypes, TriggerOpTypes } from "./operations.js";
import { reactive } from "./reactive.js";
import { hasChanged, isObject } from "./utils.js";

const RAW = Symbol("raw");
const arrayInstrumentations = {};

["includes", "indexOf", "lastIndexOf"].forEach((key) => {
  arrayInstrumentations[key] = function (...args) {
    // 现从代理对象上寻找这些方法
    const res = Array.prototype[key].apply(this, args);
    if (res < 0 || !res) {
      // 代理对象上查找不到这些方法时在从元对象中查找
      return Array.prototype[key].apply(this[RAW], args);
    }

    return res;
  };
});

// 只对这些数组方法左依赖收集，而不对这些数组内部使用数组的key做依赖收集
["push", "pop", "shift", "unshift", "splice"].forEach((key) => {
  arrayInstrumentations[key] = function (...args) {
    pauseTracking(); // 暂停依赖收集
    const res = Array.prototype[key].apply(this, args);
    resumeTracking(); // 恢复依赖收集
    return res;
  };
});

function get(target, key, receiver) {
  if (key === RAW) {
    return target;
  }

  // 依赖收集
  track(target, TrackOpTypes.GET, key);

  if (arrayInstrumentations.hasOwnProperty(key) && Array.isArray(target)) {
    return arrayInstrumentations[key];
  }

  const result = Reflect.get(target, key, receiver);
  if (isObject(result)) {
    return reactive(result);
  }
  return result;
}

function set(target, key, value, receiver) {
  const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD;
  const oldValue = target[key];
  const oldLen = Array.isArray(target) ? target.length : undefined;
  const result = Reflect.set(target, key, value, receiver);
  // 是否赋值成功（对于哪些seal，freeeze对象不做依赖收集）
  if (!result) {
    return result;
  }
  const newLen = Array.isArray(target) ? target.length : undefined;
  // 派发更新
  if (hasChanged(oldValue, value) || type === TriggerOpTypes.ADD) {
    trigger(target, type, key);
    if (Array.isArray(target) && oldLen !== newLen) {
      if (key === "length") {
        trigger(target, TriggerOpTypes.SET, "length");
      } else {
        for (let i = newLen; i < oldLen; i++) {
          trigger(target, TriggerOpTypes.DELETE, i.toString());
        }
      }
    }
  }

  return result;
}

function has(target, key) {
  track(target, TrackOpTypes.HAS, key);
  return Reflect.has(target, key);
}

function ownKeys(target) {
  track(target, TrackOpTypes.ITERATE);
  return Reflect.ownKeys(target);
}

function deleteProperty(target, key) {
  const hadKey = target.hasOwnProperty(key);
  const result = Reflect.deleteProperty(target, key);

  if (hadKey && result) {
    trigger(target, TriggerOpTypes.DELETE, key);
  }

  return result;
}

export const handlers = {
  get,
  set,
  has,
  ownKeys,
  deleteProperty,
};
